//
//  HLELowcostBleManager.m
//  HLELowcostPeripherial
//
//  Created by hle1 on 2021/8/19.
//

#import "HLELowcostBleManager.h"
#import <BabyBluetooth/BabyBluetooth.h>
#import <YYCategories/YYCategories.h>
#import <YsToolsManager/YsToolsManager.h>
#import "HLELowcostScheduler.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "HLELowcostPeripherial.h"

static NSInteger  kHLELowcostHLELowcostBleManagerTimeOut = 5;
/// 蓝牙最多连接的数量
static NSInteger  kHLELowcostHLELowcostBleMaxConnectNum = 3;


@interface HLELowcostBleManager()
@property(nonatomic, strong)  BabyBluetooth *baby;
@property(nonatomic, strong) NSMutableDictionary *dict;
@end

@implementation HLELowcostBleManager
#pragma mark - LifeCycle
+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static HLELowcostBleManager *instance = nil;
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:NULL] init];
        instance.baby = [BabyBluetooth shareBabyBluetooth];
        [[RACSignal interval:5 onScheduler:RACScheduler.mainThreadScheduler] subscribeNext:^(NSDate * _Nullable x) {
                NSLog(@"HLELowcostPeripherial:\n%@\n",instance.connectedPeripherals);
            NSLog(@"=========%@",instance.discoverPeripherals);
        }];
    });
    return instance;
}

+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}


#pragma mark - Public
- (void)sendMsgBySerialNum:(NSString *)serialNum data:(NSData *)data complete:(void (^)(BOOL isSuccess,id data))complete
{
    @weakify(self);
    HLELowcostBleInforModel *connectModel = [[self.discoverPeripherals qmui_filterWithBlock:^BOOL(HLELowcostBleInforModel *item) {
        return [item.serialNum isEqualToString:serialNum];
    }] objectOrNilAtIndex:0];
    if (connectModel) {
        if (connectModel.peripheral.state == CBPeripheralStateConnected && connectModel.notifyCharacteristics && connectModel.writeCharacteristics) {
            [self writeValue:data forCharacteristic:connectModel.writeCharacteristics peripheral:connectModel.peripheral];
        }else{
            [self toConnect:connectModel data:data];
        }
    }else{
        [self findPeripheralBySerialNum:serialNum result:^(CBPeripheral *peripheral) {
            @strongify(self);
            if (!peripheral) {
                return;
            }
            HLELowcostBleInforModel *model = [[HLELowcostBleInforModel alloc] init];
            model.peripheral = peripheral;
            model.serialNum = serialNum;
            [self toConnect:model data:data];
        }];
    }
    
    //// 回调逻辑处理
    if (((uint8_t *)data.bytes)[3] == CMD_GET_DEV_TIMING_REQ && complete) {
        NSMutableArray <NSData *>*array = @[].mutableCopy;
        NSMutableSet <NSNumber *>*timeIDarray = [NSMutableSet set];
        
        __block RACDisposable *disposable = [[[[HLEAdvertiser.sharedInstance.advertiserSubject filter:^BOOL(NSData *  _Nullable value) {
            uint8_t *byte = (uint8_t *)value.bytes;
            if (byte[3] -  ((uint8_t *)data.bytes)[3] == 1) {
                return YES;
            }else{
                return NO;
            }
        }] distinctUntilChanged] timeout:kHLELowcostHLELowcostBleManagerTimeOut onScheduler:RACScheduler.mainThreadScheduler] subscribeNext:^(NSData *  _Nullable x) {
            S_CMD_GET_DEV_TIMING_ECHO *TIMING_ECHO = (S_CMD_GET_DEV_TIMING_ECHO *)x.bytes;
            NSInteger timeID = TIMING_ECHO->tmr.combined_var & 0b111;
            if (TIMING_ECHO->total_package == 0) {
                complete(YES,@[]);
                [disposable dispose];
            }else{
                if ([timeIDarray containsObject:@(timeID)]) return;
                [array addObject:x];
                if (array.count == TIMING_ECHO->total_package) {
                    [array sortUsingComparator:^NSComparisonResult(NSData *  _Nonnull obj1, NSData *  _Nonnull obj2) {
                        S_CMD_GET_DEV_TIMING_ECHO *TIMING_ECHO1 = (S_CMD_GET_DEV_TIMING_ECHO *)obj1.bytes;
                        S_CMD_GET_DEV_TIMING_ECHO *TIMING_ECHO2 = (S_CMD_GET_DEV_TIMING_ECHO *)obj2.bytes;
                        if (TIMING_ECHO1->cur_package_index > TIMING_ECHO2->cur_package_index){
                            return NSOrderedDescending;
                        }else{
                            return NSOrderedAscending;
                        }
                    }];
                    complete(YES,array);
                    [disposable dispose];
                }
            }
            [timeIDarray addObject:@(timeID)];
        } error:^(NSError * _Nullable error) {
            complete(NO,nil);
        }];
        
        return;
    }
    
    
    if (((uint8_t *)data.bytes)[3] == CMD_GET_DEV_MODE_REQ && complete) {
        NSMutableArray *set = @[].mutableCopy;
        __block RACDisposable *disposable = [[[[HLEAdvertiser.sharedInstance.advertiserSubject filter:^BOOL(NSData *  _Nullable value) {
            uint8_t *byte = (uint8_t *)value.bytes;
            if (byte[3] -  ((uint8_t *)data.bytes)[3] == 1 || byte[3] == CMD_GET_DEV_MODE_APPEND_ECHO) {
                return YES;
            }else{
                return NO;
            }
        }] distinctUntilChanged] timeout:kHLELowcostHLELowcostBleManagerTimeOut onScheduler:RACScheduler.mainThreadScheduler] subscribeNext:^(NSData *  _Nullable x) {
            S_CMD_GET_DEV_MODE_ECHO *model_echo = (S_CMD_GET_DEV_MODE_ECHO *)x.bytes;
            if (set.count == 0 && model_echo->header.command == CMD_GET_DEV_MODE_APPEND_ECHO) {
                [disposable dispose];
                complete(NO,@[]);
                return;
            }
            uint8_t *valueByte = (uint8_t *)x.bytes;
            NSInteger cmd =  valueByte[3];
            if (cmd == CMD_GET_DEV_MODE_ECHO) {
                S_CMD_GET_DEV_MODE_ECHO *dev_model_echo = (S_CMD_GET_DEV_MODE_ECHO *)x.bytes;
                if (![set containsObject:x]) {
                    [set addObject:x];
                }
                if (dev_model_echo->append_flag != 1) {
                    [disposable dispose];
                    complete(YES,set);
                }
            }else if (cmd == CMD_GET_DEV_MODE_APPEND_ECHO){
                S_CMD_GET_DEV_MODE_APPEND_ECHO *append_echo = (S_CMD_GET_DEV_MODE_APPEND_ECHO *)x.bytes;
                if (![set containsObject:x]) {
                    [set addObject:x];
                }
                if (set.count == append_echo->total_package + 1) {
                    [disposable dispose];
                    complete(YES,set);
                }
            }
        } error:^(NSError * _Nullable error) {
            complete(NO,nil);
        }];
        return;
    }
    
    if (complete) {
        [[[[[HLEAdvertiser.sharedInstance.advertiserSubject  filter:^BOOL(NSData *_Nullable value) {
            uint8_t *byte = (uint8_t *)value.bytes;
            if (byte[3] -  ((uint8_t *)data.bytes)[3] == 1 || byte[3] == 0x0b) {
                return YES;
            }else{
                return NO;
            }
        }] filter:^BOOL(NSData *  value) {
            if (((uint8_t *)data.bytes)[3] == CMD_SET_DEV_TIMING_REQ) {
                S_CMD_SET_DEV_TIMING_REQ *TIMING_REQ = (S_CMD_SET_DEV_TIMING_REQ *)((uint8_t *)data.bytes);
                S_CMD_SET_DEV_TIMING_ECHO *TIMING_ECHO = (S_CMD_SET_DEV_TIMING_ECHO *)value.bytes;
                if (TIMING_REQ->tmr.action == TIMING_ECHO->tmr.action) {
                    return YES;
                }else{
                    return NO;
                }
            }else{
                return YES;
            }
        }] take:1] timeout:kHLELowcostHLELowcostBleManagerTimeOut onScheduler:RACScheduler.mainThreadScheduler]  subscribeNext:^(NSData *  _Nullable x) {
            complete(YES,x);
        } error:^(NSError * _Nullable error) {
            complete(NO,nil);
        }];
    }
}

- (void)disconnectBySerialNum:(NSString *)serialNum
{
    HLELowcostBleInforModel *connectModel = [[self.discoverPeripherals qmui_filterWithBlock:^BOOL(HLELowcostBleInforModel *item) {
        return [item.serialNum isEqualToString:serialNum];
    }] objectOrNilAtIndex:0];
    if (connectModel) {
        if (connectModel.peripheral) [self.baby cancelPeripheralConnection:connectModel.peripheral];
    }
}

- (void)cancelAllPeripheralsConnection
{
    [self.baby cancelAllPeripheralsConnection];
}


#pragma mark - private

- (void)toConnect:(HLELowcostBleInforModel *)bleInforModel data:(NSData *)data {
    if (self.connectedPeripherals.count == kHLELowcostHLELowcostBleMaxConnectNum) {
        HLELowcostBleInforModel *model = self.connectedPeripherals.firstObject;
        [self.baby cancelPeripheralConnection:model.peripheral];
    }
    @weakify(self);
    [self.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
        @strongify(self);
        for (CBCharacteristic *cha in service.characteristics) {
            if([[cha.UUID.UUIDString uppercaseString] isEqualToString:@"FFF3"]){
                bleInforModel.writeCharacteristics = cha;
                [RACScheduler.mainThreadScheduler afterDelay:0.2 schedule:^{
                    if (data) [self writeValue:data forCharacteristic:cha peripheral:peripheral];
                }];
            }else if([[cha.UUID.UUIDString uppercaseString] isEqualToString:@"FFF4"]){
                bleInforModel.notifyCharacteristics = cha;
                [peripheral setNotifyValue:YES forCharacteristic:cha];
                [self.baby notify:peripheral
                   characteristic:cha
                            block:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
                    if (error) return;
                    [HLEAdvertiser.sharedInstance.advertiserSubject sendNext:characteristics.value];
                    S_LIGHT_STATUS_NOTIFY *STATUS_NOTIFY = (S_LIGHT_STATUS_NOTIFY *)characteristics.value.bytes;
                    if (STATUS_NOTIFY->header.command == CMD_LIGHT_STATUS_NOTIFY) {
                        HLELowcostBleInforModel *item  =  [self.connectedPeripherals qmui_firstObjectWithBlock:^BOOL(HLELowcostBleInforModel *item) {
                            return [item.peripheral isEqual:peripheral];
                        }];
                        if (!item) return;
                        
                        RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[characteristics.value,item.serialNum]];
                        [HLELowcostBleManager.sharedInstance.heartbeatSubject sendNext:tuple];
                    }
                }];
            }
        }
    }];
    
    [self.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) {
        @strongify(self);
        bleInforModel.peripheral = peripheral;
        [self addPeripheral:bleInforModel];
        S_START_CONNECT_ECHO REC = MINI_BLUETOOTH_TAPE_LIGHT_HEADER_RX;
        REC.header.command = 0x01;
        [HLEAdvertiser.sharedInstance.advertiserSubject sendNext:[NSData dataWithBytes:(Byte *)&REC length:CONF_PAYLOAD_WIDTH]];
        [NSNotificationCenter.defaultCenter postNotificationName:@"HLELowcostPeripherialConnectSucces" object:nil userInfo:@{@"serialNum":bleInforModel.serialNum}];
    }];
    
    [self.baby setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
        @strongify(self);
        bleInforModel.peripheral = peripheral;
        [self deletePeripheral:bleInforModel];
    }];
    
    [self.baby setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
        @strongify(self);
        HLELowcostBleInforModel *bleInforModel = [[HLELowcostBleInforModel alloc] init];
        bleInforModel.peripheral = peripheral;
        [self deletePeripheral:bleInforModel];
    }];
    
    [self.baby setBlockOnFailToConnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
    }];
    
    self.baby.having(bleInforModel.peripheral).and.connectToPeripherals().discoverServices().discoverCharacteristics().begin();
}

- (void)findPeripheralBySerialNum:(NSString *)serialNum result:(void (^)(CBPeripheral *peripheral))result
{
    @weakify(self);
    RACDisposable *dispose = [RACScheduler.mainThreadScheduler afterDelay:kHLELowcostHLELowcostBleManagerTimeOut schedule:^{
        [self.baby cancelScan];
        if (result) result(nil);
    }];
    self.baby.scanForPeripherals().begin();
    //设置查找设备的过滤器
    [self.baby setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
        NSString *str = [HLELowcostScheduler getSerialNumWithAdvertisementData:advertisementData];
        return [str isEqualToString:serialNum];
    }];
    //设置扫描到设备的委托
    [self.baby setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
        @strongify(self);
        [dispose dispose];
        [self.baby cancelScan];
        if (result) result(peripheral);
    }];
}

- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic peripheral:(CBPeripheral *)peripheral
{
    if (characteristic) {
        [peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
    }else{
        HLELowcostBleInforModel *model = [[HLELowcostBleInforModel alloc] init];
        model.peripheral = peripheral;
        [self deletePeripheral:model];
        NSLog(@"HLELowcostPeripherial: error \n%@\n",@"特征值为nil");
    }
}

- (void)addPeripheral:(HLELowcostBleInforModel *)peripheral {
    HLELowcostBleInforModel *item = [[self.connectedPeripherals qmui_filterWithBlock:^BOOL(HLELowcostBleInforModel *item) {
        return [item.peripheral isEqual:peripheral.peripheral];
    }] objectOrNilAtIndex:0];
    if (item) {
        NSInteger index = [self.connectedPeripherals indexOfObject:item];
        [self willChangeValueForKey:@"connectedPeripherals"];
        [self.connectedPeripherals replaceObjectAtIndex:index withObject:peripheral];
        [self didChangeValueForKey:@"connectedPeripherals"];
    }else{
        [self willChangeValueForKey:@"connectedPeripherals"];
        [self.connectedPeripherals addObject:peripheral];
        [self didChangeValueForKey:@"connectedPeripherals"];
    }
    
    
    HLELowcostBleInforModel *discoverItem = [[self.discoverPeripherals qmui_filterWithBlock:^BOOL(HLELowcostBleInforModel *item) {
        return [item.peripheral isEqual:peripheral.peripheral];
    }] objectOrNilAtIndex:0];
    if (discoverItem) {
        NSInteger index = [self.discoverPeripherals indexOfObject:discoverItem];
        [self willChangeValueForKey:@"discoverPeripherals"];
        [self.discoverPeripherals replaceObjectAtIndex:index withObject:peripheral];
        [self didChangeValueForKey:@"discoverPeripherals"];
    }else{
        [self willChangeValueForKey:@"discoverPeripherals"];
        [self.discoverPeripherals addObject:peripheral];
        [self didChangeValueForKey:@"discoverPeripherals"];
    }
}

- (void)deletePeripheral:(HLELowcostBleInforModel  *)peripheral{
    HLELowcostBleInforModel *item = [[self.connectedPeripherals qmui_filterWithBlock:^BOOL(HLELowcostBleInforModel *item) {
        return [item.peripheral isEqual:peripheral.peripheral];
    }] objectOrNilAtIndex:0];
    if (item) {
        [self willChangeValueForKey:@"connectedPeripherals"];
        [self.connectedPeripherals removeObject:item];
        [self didChangeValueForKey:@"connectedPeripherals"];
    }
}

#pragma mark - Event
#pragma mark - Delegate

#pragma mark - Getting/Setting
- (NSMutableArray<HLELowcostBleInforModel *> *)connectedPeripherals
{
    if (!_connectedPeripherals) {
        _connectedPeripherals = @[].mutableCopy;
    }
    return _connectedPeripherals;
}

- (NSMutableArray<HLELowcostBleInforModel *> *)discoverPeripherals
{
    if (!_discoverPeripherals) {
        _discoverPeripherals = @[].mutableCopy;
    }
    return _discoverPeripherals;
}

- (NSMutableDictionary *)dict
{
    if (!_dict) {
        _dict = @{}.mutableCopy;
    }
    return _dict;
}

- (RACSubject *)heartbeatSubject
{
    if (!_heartbeatSubject) {
        _heartbeatSubject = RACSubject.subject;
    }
    return _heartbeatSubject;
}
@end
